package analizadorLexico;

import java.util.Hashtable;
import java.util.Map;

/**
 * Clase encargada de realizar el an�lisis l�xico. Es decir, el escan�o del archivo fuente y la generaci�n de tokens.
 * @author Eliana Liberman - Mat�as Carrasco
 *
 */
public class AnalizadorLexico {
	
	
	//-------- SCANNING ---------------------
	private LectorArchivo lector;
	private StringBuffer cadena;
	private String buffer;
	private int nroLinea;
	
	boolean flag_cmt_linea=false;
	boolean flag_cmt_bloque=false;
	boolean flag_espacio=false;
	boolean flag_string=false;
	
	//-----------AF--------------
	private int estadoActual;
	private char charActual;
	private int punteroBuffer;
	private String bufferAF;
	
	private static Map<String, String> palabrasReservadas;
	
	/**
	 * Constructor de la clase.
	 * @param dir : String con el path del archivo fuente a procesar.
	 */
	public AnalizadorLexico(String dir){
		lector = new LectorArchivo(dir);
		nroLinea=0;
		punteroBuffer=0;
		try {
			bufferAF=scanningBuffer();
		} catch (ExcepcionLexico e) {
			
		} 
		
		palabrasReservadas= new Hashtable<String,String>(); inicializarPalabrasReservadas();
	}
	
	
	/**
	 * M�todo p�blico que mediante un automata finito realiza el an�lisis l�xico del archivo fuente. Devuelve de a un token por vez
	 * avanzando secuencialmente por el c�digo fuente.
	 * @return : instancia de la clase Token.
	 * @throws ExcepcionLexico : lanza excepci�n ante alg�n error l�xico en el archivo.
	 */
	public Token getToken() throws ExcepcionLexico{
		StringBuffer lexema = new StringBuffer();
		estadoActual=0;
		
		boolean flag_cero=false;
		
		while(true){
			charActual=read();
			
			switch (estadoActual){
				
				case 0:{
					if(Character.isLetter(charActual) || charActual=='_'){
						estadoActual=7;
						lexema.append(charActual);
					}
					else if (Character.isDigit(charActual)){
						if(charActual=='0')
							flag_cero=true;
						
						estadoActual=8;
						lexema.append(charActual);
					}
					else{	
						switch (charActual){
							case '\0' : { return new Token("EOF","\\0",nroLinea);}
							case '(' : { return new Token("(","(",nroLinea);}
							case ')' : { return new Token(")",")",nroLinea);}
							case '+' : {//verificar si no se trata de un operador prohibido
											charActual=read();
											lexema.append(charActual);
											if (charActual=='+' || charActual=='=') //se trata de algun operador no soportado en Decaf
												throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
											else
											{
												punteroBuffer--;
												return new Token("+","+",nroLinea);
											}
										}
							case '-' : { //verificar si no se trata de un operador prohibido
											charActual=read();
											if (charActual=='-' || charActual=='=') //se trata de algun operador no soportado en Decaf
												throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
											else
											{
												punteroBuffer--;
												return new Token("-","-",nroLinea);
											}
										}
							case '{' : { return new Token("{","{",nroLinea);}
							case '}' : { return new Token("}","}",nroLinea);}
							case '[' : { return new Token("[","[",nroLinea);}
							case ']' : { return new Token("]","]",nroLinea);}
							case '.' : { return new Token(".",".",nroLinea);}
							case '*' : { 
										charActual=read();
										if (charActual=='=') //se trata de algun operador no soportado en Decaf
											throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
										else
										{
											punteroBuffer--;
											return new Token("*","*",nroLinea);
										}
									}
							case '/' : { 
										charActual=read();
										if (charActual=='=') //se trata de algun operador no soportado en Decaf
											throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
										else
										{
											punteroBuffer--;
											return new Token("/","/",nroLinea);
										}
									}
							case '%' : { 
										charActual=read();
										if (charActual=='=') //se trata de algun operador no soportado en Decaf
											throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
										else
										{
											punteroBuffer--;
											return new Token("%","%",nroLinea);
										}
								
							}
							case ',' : { return new Token(",",",",nroLinea);}
							case ';' : { return new Token(";",";",nroLinea);}
							case '>' : { estadoActual=5; break; }
							case '<' : { estadoActual=9; break; } 
							case '=' : { estadoActual=10; break; } 
							case '!' : { estadoActual=11; break; } 
							case '|' : { estadoActual=12; break; } 
							case '&' : { estadoActual=13; break; } 
							case '\'' : { estadoActual=14; lexema.append(charActual);break;  }
							case '"' : { estadoActual=15; lexema.append(charActual);break; }
							case' ':break;
							case'\n':break;
							case '~':throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
							case '?':throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
							case ':':throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
							case '^':throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
							default: {
								throw new ExcepcionLexico("linea "+nroLinea+": Caracter extra�o.");}
						}//fin switch
					}
					break;
				}//fin case0
				case 5:{
					if(charActual=='=')					
					{
						return new Token(">=",">=",nroLinea);
					}
					else if (charActual=='>') //se trata de algun operador no soportado en Decaf
							 throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
						 else
						 {// es como transici�n lambda a estado inicial
							 punteroBuffer--;
							 return new Token(">",">",nroLinea);
						 }
				}//case5
				case 9:{ //-----------------------------------
					if(charActual=='=')
					{
						return new Token("<=","<=",nroLinea);
					}
					else if (charActual=='<') //se trata de algun operador no soportado en Decaf
						 	throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
						else
						{// es como transici�n lambda a estado inicial
							punteroBuffer--;
							return new Token("<","<",nroLinea);
						}
				}//case9
				case 10:{
					if(charActual=='=')
					{
						return new Token("==","==",nroLinea);
					}
					else{// es como transici�n lambda a estado inicial
						punteroBuffer--;
						return new Token("=","=",nroLinea);
					}
				}//case10
				case 11:{
					if(charActual=='=')
					{
						return new Token("!=","!=",nroLinea);
					}
					else{// es como transici�n lambda a estado inicial
						punteroBuffer--;
						return new Token("!","!",nroLinea);
					}
				}//case11
				case 12:{
					if(charActual=='|')
					{
						return new Token("||","||",nroLinea);
					}
					else if(charActual=='=')
							throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
						else
							throw new ExcepcionLexico("linea "+nroLinea+": operador || mal formado");
					
				}//case12
				case 13:{
					if(charActual=='&')
					{
						return new Token("&&","&&",nroLinea);
					}
					if(charActual=='=')
						throw new ExcepcionLexico("linea "+nroLinea+": Operador no soportado en Decaf.");
					else
						throw new ExcepcionLexico("linea "+nroLinea+": operador && mal formado");
					
				}//case13 
				case 7:{ 
					if(Character.isLetter(charActual) || Character.isDigit(charActual) || charActual=='_'){
						lexema.append(charActual);
					}
					else{
						punteroBuffer--;
						//verificar si es palabra reservada o prohibida
						String valor=(String)palabrasReservadas.get(lexema.toString());
						if (valor == null) 
						{	//sera un token ID
							return new Token("id",lexema.toString(),nroLinea);
						
						}
						else  //esta en la tabla!
							if (valor== "palRes")
							  {//sera una pal reservada
									return new Token(lexema.toString(),lexema.toString(),nroLinea);
							  }
							 else if (valor== "palPro") //es una palabra prohibida
								   {
								 		throw new ExcepcionLexico("linea "+nroLinea+" palabra prohibida: "+lexema.toString());
								 	}
							 		else //es true o false 
							 		{
							 			return new Token("litBoolean",lexema.toString(),nroLinea);
							 		}
					}
					break;
				}//case 7 
				case 14:{// reconociendo charLit
					if(charActual!='\\' && charActual!='\'' && charActual!='\0'){
						lexema.append(charActual);
						estadoActual=16; break;
					}
					else if(charActual=='\\')
						{
							lexema.append(charActual);
							estadoActual=17; break;
						}
						else if  (charActual=='\'')
								throw new ExcepcionLexico("linea "+nroLinea+": Caracter vacio");
						
							else  throw new ExcepcionLexico("linea "+nroLinea+".Caracter mal finalizado.");
								
					
					}//case 14
				case 16: {
					if(charActual=='\'')
					{
						lexema.append(charActual); 
						return new Token("charLiteral",lexema.toString(),nroLinea);
					}else {
							throw new ExcepcionLexico("linea "+nroLinea+": .Caracter mal finalizado.");
						  }
				}//case 16
				case 17: {
							if(charActual!='\0')
							{
								lexema.append(charActual);
								estadoActual=16; break;
							}else  throw new ExcepcionLexico("linea "+nroLinea+" fin de archivo inesperado");
						}//case 17
				case 15:{// reconociendo string
						if(charActual!='"' && charActual!='\n' && charActual!='\0') //partes validas de un string
					    {
							lexema.append(charActual); 
							break;
					    }else { //termino el string
					    	if (charActual=='"')
					    	{
					    		lexema.append(charActual);
					    		//return new Token("string",lexema.toString(),nroLinea);
					    		return new Token("lit_string",lexema.toString(),nroLinea); //<<<<<<<<<<<<<<<<<<<<<<CAMBIO para evitar confusiones en A- sintactico
					    		
					    	}else //\n
					    		if(charActual=='\n') 
					    			throw new ExcepcionLexico("linea "+nroLinea+". Cadena de tipo String mal cerrada.");
					    			
					    		else //FIN DE ARCHIVO
					    			throw new ExcepcionLexico("linea "+nroLinea+". Cadena de tipo String mal cerrada.");
					    } 
				
					
				}//case 15
				
				case 8:{
					if(Character.isDigit(charActual)){
						if(flag_cero)
							throw new ExcepcionLexico("linea "+nroLinea+": Entero mal formado"); //<====================================CERO
						lexema.append(charActual);
					}
					else{	//controlar casos como 123abc
						if(Character.isLetter(charActual) || operadorInvalido(charActual))
							throw new ExcepcionLexico("linea "+nroLinea+": Identificador mal formado");
						else
						{
							punteroBuffer--;
							return new Token("intLiteral",lexema.toString(),nroLinea);
						}
					}
					break;
				}
			}
		}
		
	}
	
	private boolean operadorInvalido(char ch) {
		// TODO Auto-generated method stub
		
		if (ch=='(' || ch=='[' || ch==']'|| ch=='$'|| ch=='#'|| ch=='_') return true;
		else return false;
	}


	/**
	 * M�todo privado que lee un car�cter del buffer y avanza el puntero una posici�n, en caso del que buffer este vac�o solicita otro al �scaner.
	 * @return : char con el car�cter que actualmente se est� leyendo del buffer.
	 * @throws ExcepcionLexico : lanza excepci�n si se produce alg�n error con el buffer.
	 */
	private char read() throws ExcepcionLexico{
		
		if(punteroBuffer > (bufferAF.length()-1)){ // ver si se llego al fin del buffer
			
				bufferAF=scanningBuffer();
			
			punteroBuffer=0;
		}
		//System.out.println("PUNTERO:"+punteroBuffer+" ACTUAL:"+charActual+" BUFFER "+bufferAF+" LENGTH "+bufferAF.length());
		char c=bufferAF.charAt(punteroBuffer);
		punteroBuffer++;
		return c;
	}
	
	// -----------------FIN AF-----------------------------------------------------------------------------------------------------
	

	/**
	 * M�todo privado encargado de hacer el escaneo (elimina tabs, comentarios y compacta espacios) del buffer recibido del lector de archivos, 
	 * para luego pasarlo al automata.
	 * @return : String que representa el buffer escaneado.
	 * @throws ExcepcionLexico : lanza excepci�n si se produce alg�n error con el buffer.
	 */
	private String scanningBuffer() throws ExcepcionLexico{
		flag_cmt_linea=false;// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<agrgado con anailzador sintactico
		char actual, prox;
		buffer = lector.getBuffer();
		int linea_cmt_start=0;
		
		while(buffer!=null){
			
			cadena=new StringBuffer();
			
			int i=0;
			while(i<buffer.length()){
				
				actual=buffer.charAt(i);
				if(i== (buffer.length()-1))
					prox='\n';		
				else
					prox=buffer.charAt(i+1);
				
				//-----------  comentarios -----------------------------------
				if((actual=='/') && !flag_string){
					if(prox=='/' && !flag_cmt_bloque){ // comentario l�nea
						flag_cmt_linea=true;
					}
					else if(prox=='*'){ // comentario bloque
						flag_cmt_bloque=true;
						linea_cmt_start=nroLinea+1;
					}
				} //-------------  cerrar comentario ---------------------
				else if(flag_cmt_bloque && actual=='*' && prox=='/'){ // se cerr� el comentario
					flag_cmt_bloque=false;
					i+=2; // para no agregar "*/" consumo 2 lugares del buffer
					continue; // como cambi� el puntero del buffer, es mejor salir del ciclo actual para que analice nuevamente el car actual
				}//-------------- aparece string --------------------------
				else if(actual=='"'){// string
					if(!flag_cmt_bloque && !flag_cmt_linea){  //<<<<<<<<<<<<<<<<<<<<COMENTARIO Y COMILLAS
						if(flag_string)
							flag_string=false;
						else if(prox!='\'')     //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<PARCHE COMENTARIO Y COMILLAS
							flag_string=true;
					}
				}//------------- espacios --------------------
				else if(actual==' '){
					if(!flag_espacio && !flag_string && !flag_cmt_bloque && !flag_cmt_linea){
						flag_espacio=true;
						cadena.append(actual);
					}
				}
				
				
				
				if(actual!=' ' && flag_espacio){ // ver si ya pasaron todos los espacios
					
					flag_espacio=false;
				}
				
				if(actual=='\t'&&!flag_espacio)// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<PARCHE TAB!!!!!!!!!!!!!!!
					cadena.append(" ");
				
				if(actual!='\t' && (!flag_espacio || flag_string) && !flag_cmt_bloque && !flag_cmt_linea){ // algo que se pueda escribir
					
					cadena.append(actual);
				}
				
				
				i++;//incremento el puntero del buffer
				
			} // while de recorrido del buffer
		
			nroLinea++; // incremento el contador de lineas
			
			if(cadena.length()>0){ // verifico no devolver un buffer vac�o
				//System.out.println(nroLinea+")length: "+cadena.length()+"-"+cadena);
				cadena.append('\n');
				return cadena.toString();
			}
			else{
				buffer=lector.getBuffer();
				if(flag_cmt_linea)
					flag_cmt_linea=false;
				if(buffer==null){
					if(flag_cmt_bloque)
						throw new ExcepcionLexico("Linea N�:"+linea_cmt_start+" - Se llego a EOF sin que cierre el comentario");
					return "\0";
				}
			}
		
		} // while-if
		
		if(flag_cmt_bloque)
			throw new ExcepcionLexico("Linea N�:"+linea_cmt_start+" - Se llego a EOF sin que cierre el comentario");
		
		return "\0";
	}

	/**
	 * M�todo privado que se encarga de inicializar el mapeo que contiene tanto las palabras reservadas como prohibidas.
	 */
	private static void inicializarPalabrasReservadas(){
		//palabras reservadas
		palabrasReservadas.put("class","palRes" );
		palabrasReservadas.put("if", "palRes");
		palabrasReservadas.put("while", "palRes");
		palabrasReservadas.put("else", "palRes");
		palabrasReservadas.put("new", "palRes");
		palabrasReservadas.put("return", "palRes");
		palabrasReservadas.put("super", "palRes");
		palabrasReservadas.put("this", "palRes");
		palabrasReservadas.put("extends", "palRes");
		palabrasReservadas.put("boolean", "palRes");
		palabrasReservadas.put("char", "palRes");
		palabrasReservadas.put("int", "palRes");
		palabrasReservadas.put("for", "palRes");
		palabrasReservadas.put("void", "palRes");
		palabrasReservadas.put("String", "palRes");
		palabrasReservadas.put("classDef", "palRes");
		
		//literales boolean
		palabrasReservadas.put("true", "boolLit");
		palabrasReservadas.put("false", "boolLit");
		
		//palabras prohibidas
		palabrasReservadas.put("abstract", "palPro");
		palabrasReservadas.put("default", "palPro");
		palabrasReservadas.put("long", "palPro");
		palabrasReservadas.put("switch", "palPro");
		palabrasReservadas.put("try", "palPro");
		palabrasReservadas.put("byte", "palPro");
		palabrasReservadas.put("do", "palPro");
		palabrasReservadas.put("implements", "palPro");
		palabrasReservadas.put("native", "palPro");
		palabrasReservadas.put("synchronized", "palPro");
		palabrasReservadas.put("volatile", "palPro");
		palabrasReservadas.put("case", "palPro");
		palabrasReservadas.put("double", "palPro");
		palabrasReservadas.put("import", "palPro");
		palabrasReservadas.put("goto", "palPro");
		palabrasReservadas.put("throw", "palPro");
		palabrasReservadas.put("catch", "palPro");
		palabrasReservadas.put("final", "palPro");
		palabrasReservadas.put("instanceof", "palPro");
		palabrasReservadas.put("package", "palPro");
		palabrasReservadas.put("throws", "palPro");
		palabrasReservadas.put("const", "palPro");
		palabrasReservadas.put("finally", "palPro");
		palabrasReservadas.put("interface", "palPro");
		palabrasReservadas.put("short", "palPro");
		palabrasReservadas.put("transient", "palPro");
		palabrasReservadas.put("break", "palPro");
		palabrasReservadas.put("continue", "palPro");
		palabrasReservadas.put("private", "palPro");
		palabrasReservadas.put("protected", "palPro");
		palabrasReservadas.put("public", "palPro");
		palabrasReservadas.put("static", "palPro");
		palabrasReservadas.put("byvalue", "palPro");
		palabrasReservadas.put("none", "palPro");
		palabrasReservadas.put("cast", "palPro");
		palabrasReservadas.put("operator", "palPro");
		palabrasReservadas.put("future", "palPro");
		palabrasReservadas.put("outer", "palPro");
		palabrasReservadas.put("generic", "palPro");
		palabrasReservadas.put("rest", "palPro");
		palabrasReservadas.put("inner", "palPro");
		palabrasReservadas.put("var", "palPro");
		palabrasReservadas.put("float", "palPro");
	}
}
